场景说明
在实际业务中,可能需要同时连接多个同类型的数据库实例(如两个 MySQL),用于数据隔离、读写分离等场景。NestJS + TypeORM 原生支持这种配置方式。
配置多数据库连接
在 AppModule 中注册两个连接
// app.module.ts
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
// 默认数据库连接(端口 3306)
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
type: 'mysql',
host: configService.get('DB_HOST'),
port: configService.get<number>('DB_PORT'), // 3306
username: configService.get('DB_USERNAME'),
password: configService.get('DB_PASSWORD'),
database: configService.get('DB_DATABASE'),
entities: [User],
synchronize: true,
}),
}),
// 第二个数据库连接(端口 3307),必须设置 name
TypeOrmModule.forRootAsync({
name: 'mysql1', // 关键:唯一标识此连接
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
type: 'mysql',
host: configService.get('DB_HOST'),
port: 3307, // 不同的端口
username: configService.get('DB_USERNAME'),
password: configService.get('DB_PASSWORD'),
database: configService.get('DB_DATABASE'),
entities: [User],
synchronize: true,
}),
}),
// forFeature 中指定连接
TypeOrmModule.forFeature([User]), // 默认连接
TypeOrmModule.forFeature([User], 'mysql1'), // mysql1 连接
],
})
export class AppModule {}
typescript
关键注意事项
name属性是区分多个连接的核心,不设置会导致后一个连接覆盖前一个forFeature()必须传递对应的连接名称,否则实体会注册到默认连接name属性设置在TypeOrmModule.forRoot()的选项对象上
在 Controller 中使用多数据库
// app.controller.ts
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
@Controller('api/v1')
export class AppController {
constructor(
// 默认数据库的 Repository
@InjectRepository(User)
private userRepository: Repository<User>,
// mysql1 数据库的 Repository
@InjectRepository(User, 'mysql1')
private userRepository1: Repository<User>,
) {}
@Get('hello')
async getData() {
// 从默认数据库查询
const defaultData = await this.userRepository.find();
return { db: 'default', data: defaultData };
}
@Get('hello/v2')
async getDataFromDb1() {
// 从 mysql1 数据库查询
const db1Data = await this.userRepository1.find();
return { db: 'db1', data: db1Data };
}
}
typescript
注入方式:在 @InjectRepository() 的第二个参数传入连接名称。
forRootAsync vs forRoot
| 方式 | 特点 | 适用场景 |
|---|---|---|
forRoot | 配置项写死在代码中 | 简单项目、快速原型 |
forRootAsync | 通过 ConfigService 读取配置 | 正式项目,配置可管理 |
forRootAsync 的优势在于可以将数据库连接信息放在 .env 文件中,避免硬编码,方便切换环境。
通过 Query 参数动态选择数据库
使用 @Query() 装饰器获取查询参数,根据参数值选择不同的 Repository:
@Get('data')
async getDataWithDb(@Query('db') db?: string) {
if (db === 'mysql1') {
const data = await this.userRepository1.find();
return { db: 'db1', data };
}
const data = await this.userRepository.find();
return { db: 'default', data };
}
typescript
请求示例:GET /api/v1/data?db=mysql1
这种方案的问题
每个路由方法都需要手动判断 db 参数并选择对应的 Repository,当接口数量增多时代码重复度极高。更好的解决方案包括:
- 抽象公共 Repository(下节内容):在中间层封装数据库选择逻辑
- NestJS 拦截器(Interceptor):在请求层统一处理
- 自定义装饰器:声明式指定数据库选择策略
验证多数据库连接
# 查询默认数据库(3306)
curl http://localhost:3000/api/v1/hello
# 查询 mysql1 数据库(3307)
curl http://localhost:3000/api/v1/hello/v2
# 通过 query 参数动态选择
curl http://localhost:3000/api/v1/data?db=mysql1
bash
返回结果中的 db 字段可以帮助确认数据来源于哪个数据库。
小结
- 多个同类型数据库通过
name属性区分,forFeature()中传入对应名称 forRootAsync比forRoot更灵活,推荐在生产项目中使用@Query()可以实现简单的动态数据库选择,但代码重复度高- 后续通过抽象 Repository 或拦截器可以优雅地解决重复问题
↑